/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dragoncon.dcsched.ui;

import com.dragoncon.dcsched.R;
import com.dragoncon.dcsched.provider.ScheduleContract;
import com.dragoncon.dcsched.provider.ScheduleContract.Rooms;
import com.dragoncon.dcsched.util.ActivityHelper;
import com.dragoncon.dcsched.util.AnalyticsUtils;
import com.dragoncon.dcsched.util.Maps;
import com.dragoncon.dcsched.util.NotifyingAsyncQueryHandler;
import com.dragoncon.dcsched.util.ParserUtils;

import static com.dragoncon.dcsched.util.ParserUtils.splitComma;

import com.larvalabs.svgandroid.SVG;
import com.larvalabs.svgandroid.SVGParser;

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PictureDrawable;
import android.graphics.Picture;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.ZoomButtonsController;

import java.util.Arrays;
import java.util.HashMap;

/**
 * Shows a {@link WebView} with a map of the conference venue.
 */
public class MapFragment extends Fragment
    implements ZoomButtonsController.OnZoomListener,
	       NotifyingAsyncQueryHandler.AsyncQueryListener,
	       View.OnTouchListener {
    private static final String TAG = "MapFragment";

    /**
     * When specified, will automatically point the map to the requested room.
     */
    public static final String EXTRA_ROOM = "com.dragoncon.dcsched.extra.ROOM";

    private static final String MAP_JSI_NAME = "MAP_CONTAINER";
    private static boolean CLEAR_CACHE_ON_LOAD = false;

    private static final float ZOOM_RATIO = 1.5f;

    private static class Floor implements Comparable<Floor> {
	String bldgName;
	String floorName;
	Floor prev;
	Floor next;
	int mapType;
	int mapRes;

	public Floor(String b, String f, Floor p, Floor n, int type, int res) {
	    bldgName = b;
	    floorName = f;
	    prev = p;
	    next = n;
	    mapType = type;
	    mapRes = res;
	}

	public String getTag() {
	    return bldgName+floorName;
	}

	public String toString() {
	    return bldgName+": "+floorName;
	}

	public int compareTo(Floor o) {
	    return this.toString().compareTo(o.toString());
	}
    }

    private static final HashMap<String, Floor> sFloorMap = buildFloorMap();
    private static Floor mCurrentFloor = null;

    private static final String BUILDING_HYATT = "Hyatt";
    private static final String BUILDING_MARRIOTT = "Marriott";
    private static final String BUILDING_HILTON = "Hilton";
    private static final String BUILDING_SHERATON = "Sheraton";
    private static final String BUILDING_WESTIN = "Westin";
    private static final int UNKNOWN_FLOOR = R.raw.android;

    private static final int MAP_TYPE_BMP = 0;
    private static final int MAP_TYPE_SVG = 1;

    private NotifyingAsyncQueryHandler mHandler;

    private Uri mRoomUri;

    private ImageView mMapView;
    private boolean mMapInitialized = false;
    private Matrix mMapMatrix;
    private ZoomButtonsController mMapZoom;
    private PictureDrawable mMapDrawable;
    private FrameLayout mMapFrame;
    private BitmapFactory mMapLoader;

    private Spinner mMapBuilding;

    private float mRoomX;
    private float mRoomY;
    private String mRoomBldg;
    private String mRoomFloor;

    private int mCurrentX;
    private int mCurrentY;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new NotifyingAsyncQueryHandler(getActivity().getContentResolver(), this);

        final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());
        mRoomUri = intent.getData();

        setHasOptionsMenu(true);
        AnalyticsUtils.getInstance(getActivity()).trackPageView("/Map");
    }

    private class BuildingSelectedListener implements AdapterView.OnItemSelectedListener {
	private boolean mLayoutDone = false;

	@Override
	public void onItemSelected(AdapterView<?> parent,
				   View view, int pos, long id) {
	    if(mRoomUri == null || mLayoutDone) {
		// Don't allow the spinner to set the default map unless
		// the lookup and display has finished.
		Log.d(TAG, "Spinner changing floor to pos "+pos);

		mCurrentFloor = (Floor)parent.getItemAtPosition(pos);
		loadMap();
	    }
	    mLayoutDone = true;
	}

	@Override
	public void onNothingSelected(AdapterView parent) {
	    // Do nothing.
	}
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_map, null);

        // For some reason, if we omit this, NoSaveStateFrameLayout thinks we are
        // FILL_PARENT / WRAP_CONTENT, making the progress bar stick to the top of the activity.
        root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.FILL_PARENT));

	mMapLoader = new BitmapFactory();

        mMapFrame = (FrameLayout)root.findViewById(R.id.map_frame);
        mMapView = (ImageView)root.findViewById(R.id.map_view);
	mMapView.setOnTouchListener(this);

	mMapDrawable = new PictureDrawable(null);

	mMapZoom = new ZoomButtonsController(mMapFrame);
	mMapZoom.setOnZoomListener(this);
	mMapZoom.setAutoDismissed(true);
	// attach zoom buttons to frame
	mMapFrame.addView(mMapZoom.getContainer());

        // Set the background color to white
        mMapView.setBackgroundColor(Color.WHITE);

	// Set up the scaling matrix
	mMapMatrix = new Matrix();
	mMapMatrix.reset();

	// Set the transform matrix
	mMapView.setImageMatrix(mMapMatrix);

	/* Set up building selector */
	mMapBuilding = (Spinner) root.findViewById(R.id.map_building);

	Floor[] allFloors = sFloorMap.values().toArray(new Floor[0]);
	Arrays.sort(allFloors);
	ArrayAdapter<Floor> adapter = new ArrayAdapter<Floor>(getActivity(), android.R.layout.simple_spinner_item, allFloors);
	adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
	mMapBuilding.setAdapter(adapter);
	mMapBuilding.setOnItemSelectedListener(new BuildingSelectedListener());

	final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());

	String showRoomId = null;

	// listview listener will show default floor
	// query will run in onActivityCreated

        return root;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (mRoomUri == null) {
            return;
        }

        // Start background query to load room details
        mHandler = new NotifyingAsyncQueryHandler(getActivity().getContentResolver(), this);
        mHandler.startQuery(mRoomUri, RoomsQuery.PROJECTION);
    }

    /**
     * {@inheritDoc}
     */
    public void onQueryComplete(int token, Object cookie, Cursor cursor) {
        if (getActivity() == null) {
            return;
        }

        try {
            if (!cursor.moveToFirst()) {
                return;
            }

            final String nameString = cursor.getString(RoomsQuery.ROOM_NAME);

	    // record floor and building so we only draw the marker on the correct floor
            mRoomBldg = cursor.getString(RoomsQuery.ROOM_BUILDING);
            mRoomFloor = cursor.getString(RoomsQuery.ROOM_FLOOR);

	    mRoomX = cursor.getFloat(RoomsQuery.ROOM_POSITION_X);
	    mRoomY = cursor.getFloat(RoomsQuery.ROOM_POSITION_Y);
	    
	    Log.d(TAG, "Loaded details for "+nameString+" ["+mRoomBldg+":"+mRoomFloor+"@"+mRoomX+","+mRoomY+"]");

	    loadMapByName(mRoomBldg, mRoomFloor);
        } finally {
            cursor.close();
        }
    }

    private void loadMapByName(String building, String floor) {
	mCurrentFloor = sFloorMap.get(building+floor);

	// loadMap will reference mCurrentFloor
	loadMap();
    }

    private static HashMap<String, Floor> buildFloorMap() {
        final HashMap<String, Floor> map = Maps.newHashMap();
	Floor tmp1, tmp2;

	/* Hyatt */
	tmp1 = new Floor(BUILDING_HYATT, "Ballroom", null, null, MAP_TYPE_BMP, R.raw.hyatt_ballroom);
	
	tmp2 = new Floor(BUILDING_HYATT, "Conference", tmp1, null, MAP_TYPE_BMP, R.raw.hyatt_conference);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_HYATT, "Embassy", tmp1, null, MAP_TYPE_BMP, R.raw.hyatt_embassy);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;
	
	tmp2 = new Floor(BUILDING_HYATT, "Exhibit", tmp1, null, MAP_TYPE_BMP, R.raw.hyatt_exhibit);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_HYATT, "International", tmp1, null, MAP_TYPE_BMP, R.raw.hyatt_international);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);

	map.put(tmp2.getTag(), tmp2);

	/* Marriott */
	tmp1 = new Floor(BUILDING_MARRIOTT, "International", null, null, MAP_TYPE_BMP, R.raw.atlmqf01);
	
	tmp2 = new Floor(BUILDING_MARRIOTT, "Marquis", tmp1, null, MAP_TYPE_BMP, R.raw.atlmqf02);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_MARRIOTT, "Lobby", tmp1, null, MAP_TYPE_BMP, R.raw.atlmqf03);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_MARRIOTT, "Atrium", tmp1, null, MAP_TYPE_BMP, R.raw.atlmqf04);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_MARRIOTT, "Skyline", tmp1, null, MAP_TYPE_BMP, R.raw.atlmqf99);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);

	map.put(tmp2.getTag(), tmp2);

	/* Hilton */
	tmp1 = new Floor(BUILDING_HILTON, "Lower", null, null, MAP_TYPE_BMP, R.raw.atlhil2);
	
	tmp2 = new Floor(BUILDING_HILTON, "1", tmp1, null, MAP_TYPE_BMP, R.raw.atlhil1f);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_HILTON, "2", tmp1, null, MAP_TYPE_BMP, R.raw.atlhil2f);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_HILTON, "3", tmp1, null, MAP_TYPE_BMP, R.raw.atlhil3f);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);

	map.put(tmp2.getTag(), tmp2);
	
	/* Sheraton */
	tmp1 = new Floor(BUILDING_SHERATON, "1", null, null, MAP_TYPE_BMP, R.raw.sheraton1);
	
	tmp2 = new Floor(BUILDING_SHERATON, "2", tmp1, null, MAP_TYPE_BMP, R.raw.sheraton2);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_SHERATON, "3", tmp1, null, MAP_TYPE_BMP, R.raw.sheraton3);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);

	map.put(tmp2.getTag(), tmp2);

	/* Westin */
	tmp1 = new Floor(BUILDING_WESTIN, "6", null, null, MAP_TYPE_BMP, R.raw.westin6);
	
	tmp2 = new Floor(BUILDING_WESTIN, "7", tmp1, null, MAP_TYPE_BMP, R.raw.westin7);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);
	tmp1 = tmp2;

	tmp2 = new Floor(BUILDING_WESTIN, "8", tmp1, null, MAP_TYPE_BMP, R.raw.westin8);
	tmp1.next = tmp2;
	map.put(tmp1.getTag(), tmp1);

	map.put(tmp2.getTag(), tmp2);

        return map;
    }

    private void loadMap() {
	Picture pic = new Picture();
	int picX, picY;
	Canvas tmpCanvas;
	int type, res;

	final ActivityHelper activityHelper = ((BaseActivity) getActivity()).getActivityHelper();
	    
	if(mCurrentFloor == null) {
	    activityHelper.setActionBarTitle("Unknown Floor");

	    type = MAP_TYPE_SVG;
	    res = UNKNOWN_FLOOR;
	} else {
	    activityHelper.setActionBarTitle(mCurrentFloor.toString());
	
	    type = mCurrentFloor.mapType;
	    res = mCurrentFloor.mapRes;
	}

	if(type == MAP_TYPE_SVG) {
	    // Parse the SVG file from the resource
	    final SVG svg = SVGParser.getSVGFromResource(getResources(), res);
	    // Get a drawable from the parsed SVG and set it as the drawable for the ImageView
	    final Picture tmpPic = svg.getPicture();
	    picX = tmpPic.getWidth();
	    picY = tmpPic.getHeight();

	    Log.d(TAG, "Loaded SVG for "+mCurrentFloor+": "+picX+"x"+picY);

	    // using the SVG's canvas does not appear to work
	    tmpCanvas = pic.beginRecording(picX, picY);
	    tmpCanvas.drawPicture(tmpPic);
	} else {
	    // assume this is a bitmap of some form
	    final Bitmap img = mMapLoader.decodeResource(getResources(), res);
	    picX = img.getWidth();
	    picY = img.getHeight();

	    Log.d(TAG, "Loaded raster for "+mCurrentFloor+": "+picX+"x"+picY);

	    // copy bitmap onto Picture
	    tmpCanvas = pic.beginRecording(picX, picY);
	    tmpCanvas.drawBitmap(img, new Matrix(), null);
	}

	// we want to get the center of the whole frame, in case the map
	// image is obstructed by, for instance, the zoom buttons.
	Rect visible = new Rect();
	mMapFrame.getWindowVisibleDisplayFrame(visible);

	mMapMatrix.reset();

	if(mRoomX != 0 && mRoomY != 0 && mCurrentFloor != null &&
	   mCurrentFloor.bldgName.equals(mRoomBldg) &&
	   mCurrentFloor.floorName.equals(mRoomFloor)) {
	    // draw marker only if we have a specific room and we're on that floor
	    final Bitmap marker = mMapLoader.decodeResource(getResources(), R.raw.marker);
	    tmpCanvas.drawBitmap(marker,
				 mRoomX-marker.getWidth()/2,
				 mRoomY-marker.getHeight(), null);

	    // translate to center of screen
	    mMapMatrix.postTranslate(-mRoomX+visible.exactCenterX(), 
				     -mRoomY+visible.exactCenterY());
	}

	pic.endRecording(); // tmpCanvas no longer valid
	mMapDrawable.setPicture(pic);
        mMapView.setImageDrawable(mMapDrawable);
	mMapView.setImageMatrix(mMapMatrix);

	// force a redraw
	mMapView.invalidate();
    }


    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.map_menu_items, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_building) {
	    mMapBuilding.performClick();
        } else if (item.getItemId() == R.id.menu_prev) {
	    if(mCurrentFloor != null && mCurrentFloor.prev != null) {
		mCurrentFloor = mCurrentFloor.prev;
		loadMap();
	    }
            return true;
        } else if (item.getItemId() == R.id.menu_next) {
	    if(mCurrentFloor != null && mCurrentFloor.next != null) {
		mCurrentFloor = mCurrentFloor.next;
		loadMap();
	    }
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override 
    public boolean onTouch(View v, MotionEvent event) {
	if(v == mMapView) {
	    switch (event.getAction()) {
	    case MotionEvent.ACTION_DOWN: {
		mCurrentX = (int) event.getRawX();
		mCurrentY = (int) event.getRawY();
		//mMapZoom.setVisible(true);
		break;
	    }

	    case MotionEvent.ACTION_MOVE: {
		int x2 = (int) event.getRawX();
		int y2 = (int) event.getRawY();
		mMapMatrix.postTranslate(x2-mCurrentX, y2-mCurrentY);
		mMapView.setImageMatrix(mMapMatrix);
		mCurrentX = x2;
		mCurrentY = y2;
		break;
	    }   
	    case MotionEvent.ACTION_UP: {
		if(event.getDownTime() > 2000) {
		    // consider this a long click
		    final float[] touchPt = new float[2];
		    touchPt[0] = event.getX();
		    touchPt[1] = event.getY();
		    Matrix inv = new Matrix();
		    mMapMatrix.invert(inv);

		    inv.mapPoints(touchPt);
		    Log.d(TAG, "Long click at "+touchPt[0]+","+touchPt[1]);
		}
		break;
	    }
	    }
	    return true; 
	}

	return false;
    }

    public void onVisibilityChanged(boolean visible) {
    }

    @Override
    public void onZoom(boolean zoomIn) {
	Rect visible = new Rect();
	Matrix inv = new Matrix();
	mMapMatrix.invert(inv);
	// we want to get the center of the whole frame, in case the map
	// image is obstructed by, for instance, the zoom buttons.
	mMapFrame.getWindowVisibleDisplayFrame(visible);
	final float[] center = new float[2];
	center[0] = visible.exactCenterX();
	center[1] = visible.exactCenterY();
	// map points on the screen to a point on the image itself.
	inv.mapPoints(center);

	if(zoomIn) {
	    mMapMatrix.preScale(ZOOM_RATIO, ZOOM_RATIO, center[0], center[1]);
	} else {
	    mMapMatrix.preScale(1/ZOOM_RATIO, 1/ZOOM_RATIO, center[0], center[1]);
	}
	mMapView.setImageMatrix(mMapMatrix);
    }

    public void panLeft(float screenFraction) {
	// TODO
    }

    public void openContentInfo(String roomId) {
	final String possibleTrackId = ParserUtils.translateTrackIdAlias(roomId);
	final Intent intent;
	if (ParserUtils.LOCAL_TRACK_IDS.contains(possibleTrackId)) {
	    // This is a track; open up the sandbox for the track, since room IDs that are
	    // track IDs are sandbox areas in the map.
	    Uri trackVendorsUri = ScheduleContract.Tracks.buildVendorsUri(possibleTrackId);
	    intent = new Intent(Intent.ACTION_VIEW, trackVendorsUri);
	} else {
	    Uri roomUri = Rooms.buildSessionsDirUri(roomId);
	    intent = new Intent(Intent.ACTION_VIEW, roomUri);
	}
	getActivity().runOnUiThread(new Runnable() {
                public void run() {
                    ((BaseActivity) getActivity()).openActivityOrFragment(intent);
                }
            });
    }

    public void onMapReady() {
	if (Log.isLoggable(TAG, Log.DEBUG)) {
	    Log.d(TAG, "onMapReady");
	}

	final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());

	String showRoomId = null;
	if (!mMapInitialized && intent.hasExtra(EXTRA_ROOM)) {
	    showRoomId = intent.getStringExtra(EXTRA_ROOM);
	}

	mMapInitialized = true;
    }

    private interface RoomsQuery {
        String[] PROJECTION = {
                ScheduleContract.Rooms.ROOM_ID,
                ScheduleContract.Rooms.ROOM_NAME,
                ScheduleContract.Rooms.ROOM_BUILDING,
                ScheduleContract.Rooms.ROOM_FLOOR,
                ScheduleContract.Rooms.ROOM_POSITION_X,
                ScheduleContract.Rooms.ROOM_POSITION_Y,
        };

	int ROOM_ID = 0;
	int ROOM_NAME = 1;
	int ROOM_BUILDING = 2;
	int ROOM_FLOOR = 3;
	int ROOM_POSITION_X = 4;
	int ROOM_POSITION_Y = 5;
    }
}
